package com.app.config;

import cn.hutool.core.util.IdUtil;
import com.app.domain.dto.MethodInvokeTraceDto;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 调用链路追踪日志
 *
 * @author ch
 * @date 2022/12/16 19:03
 */
@Order(4)
@Aspect
@Component
public class MethodInvokeTraceAspect {

//    private static final String POINTCUT = "@annotation(com.app.config.MethodInvokeTrace)";

    private static final String POINTCUT = "execution(* com.app.service.*.*(..))";

    public static final ThreadLocal<MethodInvokeTraceDto> METHOD_INVOKE_TRACE_THREAD_LOCAL = new ThreadLocal<>();

    public static final ThreadLocal<Integer> SERIAL_NO_THREAD_LOCAL = new ThreadLocal<>();

    @Around(value = POINTCUT)
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodInvokeTraceDto methodInvokeTraceDto = new MethodInvokeTraceDto();

        // 获取请求的类型和方法名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String className = method.getDeclaringClass().getSimpleName();
        String methodName = method.getName();

        // 获取方法调用时长
        long start = System.currentTimeMillis();

        // 获取执行序号
        Integer serialNo = SERIAL_NO_THREAD_LOCAL.get();
        serialNo = null != serialNo ? serialNo + 1 : 1;
        SERIAL_NO_THREAD_LOCAL.set(serialNo);

        methodInvokeTraceDto.setId(IdUtil.objectId());
        methodInvokeTraceDto.setClassName(className);
        methodInvokeTraceDto.setMethodName(methodName);
        methodInvokeTraceDto.setSerialNo(serialNo);
        methodInvokeTraceDto.setStart(start);

        MethodInvokeTraceDto parent = METHOD_INVOKE_TRACE_THREAD_LOCAL.get();
        if (null != parent) {
            methodInvokeTraceDto.setParentId(parent.getId());
            methodInvokeTraceDto.setParent(parent);
        }
        METHOD_INVOKE_TRACE_THREAD_LOCAL.set(methodInvokeTraceDto);

        // 调用方法
        Object result = joinPoint.proceed();

        long end = System.currentTimeMillis();
        long duration = end - start;
        methodInvokeTraceDto.setEnd(end);
        methodInvokeTraceDto.setDuration(duration);

        METHOD_INVOKE_TRACE_THREAD_LOCAL.remove();
        if (null != parent) {
            METHOD_INVOKE_TRACE_THREAD_LOCAL.set(parent);
        }
        // todo: methodInvokeTraceDto入库
        System.out.println(methodInvokeTraceDto);
        return result;
    }

    @AfterThrowing(pointcut = POINTCUT, throwing = "ex")
    public void doException(JoinPoint joinPoint, Exception ex) {
        MethodInvokeTraceDto methodInvokeTraceDto = METHOD_INVOKE_TRACE_THREAD_LOCAL.get();
        if (null == methodInvokeTraceDto) {
            return;
        }

        MethodInvokeTraceDto parent = methodInvokeTraceDto.getParent();
        if (null != parent) {
            METHOD_INVOKE_TRACE_THREAD_LOCAL.set(parent);
        } else {
            METHOD_INVOKE_TRACE_THREAD_LOCAL.remove();
        }

        long end = System.currentTimeMillis();
        methodInvokeTraceDto.setEnd(end);
        methodInvokeTraceDto.setDuration(methodInvokeTraceDto.getStart() - end);

        SERIAL_NO_THREAD_LOCAL.remove();
        // todo: methodInvokeTraceDto入库
        System.out.println(methodInvokeTraceDto);
    }

}
